package me.wz.util.sdbc;

import me.wz.util.Assert;
import me.wz.util.exception.RTException;

import java.beans.IntrospectionException;
import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import javax.persistence.Column;
import javax.persistence.Id;
import javax.persistence.Table;

public abstract class Metadata {
    protected Class<?> clazz;
    protected String tableName;
    private List<Field> fields = new ArrayList<Field>();
    private List<Field> idFields = new ArrayList<Field>();
    private Map<String, String> column2field = new LinkedHashMap<String, String>();
    private Map<String, String> field2column = new LinkedHashMap<String, String>();

    public Metadata(Class<?> clazz) {
        this.clazz = clazz;
    }

    public Metadata initialize() throws IntrospectionException {
        Class<?> cls = clazz;
        tableName = cls.getSimpleName();
        Table table = cls.getAnnotation(Table.class);
        while(table == null) {
            cls = cls.getSuperclass();
            if( cls == null ) {
                cls = clazz;
                break;
            }
            table = cls.getAnnotation(Table.class);
        }
        if( table != null ) {
            if( !isBlank(table.name()) ) {
                tableName = isBlank(table.schema()) ? table.name() : table.schema() + "." + table.name();
            }
        }

        Map<String, Field> mapp = new LinkedHashMap<String, Field>();
        for(Field field : cls.getDeclaredFields()){
            Column column = field.getAnnotation(Column.class);
            if( column != null ) {
                if( !isBlank(column.name()) ) {
                    column2field.put(column.name().toUpperCase(), field.getName());
                    field2column.put(field.getName(), column.name().toUpperCase());
                }
            }
            mapp.put(field.getName().toUpperCase(), field);
        }

        for(String column : getColumnNames()) {
            Field field = mapp.get(column.replaceAll("_", "").toUpperCase());
            if( field != null && !column2field.containsKey(column) ) {
                column2field.put(column.toUpperCase(), field.getName());
                field2column.put(field.getName(), column.toUpperCase());
            }
        }

        fields = new ArrayList<Field>(mapp.values());
        for(Field field : fields) {
            Id id = field.getAnnotation(Id.class);
            if( id != null ) {
                idFields.add(field);
            }
        }

        for(String column : getPKColumnNames()) {
            Field field = mapp.get(column.replaceAll("_", "").toUpperCase());
            if( field != null && !idFields.contains(field) ) {
                idFields.add(field);
            }
        }

        return this;
    }
    protected abstract  String[] getPKColumnNames();
    protected abstract String[] getColumnNames();
    public Field getField(String column) {
        if( column != null ) {
            String fn = column2field.get(column.toUpperCase());
            if( fn != null ) {
                for(Field field : fields) {
                    if( fn.equals(field.getName()) ) {
                        return field;
                    }
                }
            }
        }
        return null;
    }
    public String getColumn(Field field) {
        return field2column.get(field.getName());
    }
    public boolean isId(Field field) {
        return idFields.contains(field);
    }
    private boolean isBlank(Object str){ return str == null || str.toString().trim().equals(""); }

    public Class<?> getClazz() {
        return clazz;
    }

    public void setClazz(Class<?> clazz) {
        this.clazz = clazz;
    }

    public String getTableName() {
        return tableName;
    }

    public void setTableName(String tableName) {
        this.tableName = tableName;
    }

    public Map<String, String> getColumn2field() {
        return column2field;
    }

    public void setColumn2field(Map<String, String> column2field) {
        this.column2field = column2field;
    }

    public List<Field> getFields() {
        return fields;
    }

    public void setFields(List<Field> fields) {
        this.fields = fields;
    }

    public List<Field> getIdFields() {
        return idFields;
    }

    public void setIdFields(List<Field> idFields) {
        this.idFields = idFields;
    }

    public static abstract class RDSMetadata extends Metadata {

        public RDSMetadata(Class<?> clazz) {
            super(clazz);
        }

        protected abstract Connection getConnection() throws SQLException;

        @Override
        protected String[] getPKColumnNames() {
            Connection connection = null;
            ResultSet rs = null;
            try {
                connection = getConnection();
                rs = connection.getMetaData().getPrimaryKeys(connection.getCatalog(), connection.getSchema(), tableName);
                List<String> rets = new ArrayList<String>();
                while( rs.next() ) {
                    rets.add(rs.getString("COLUMN_NAME"));
                }
                return rets.toArray(new String[]{});
            }
            catch (SQLException e) {
                throw new RTException(e.getMessage(), e);
            }
            finally {
                close(rs);
                close(connection);
            }
        }

        @Override
        protected String[] getColumnNames() {
            Connection connection = null;
            ResultSet rs = null;
            try {
                connection = getConnection();
                rs = connection.createStatement().executeQuery("select * from " + tableName + " where 1=2");
                String[] rets = new String[rs.getMetaData().getColumnCount()];
                for(int i=0; i<rs.getMetaData().getColumnCount(); i++){
                    rets[i] = rs.getMetaData().getColumnName(i+1);
                }
                return rets;
            }
            catch (SQLException e) {
                throw new RTException(e.getMessage(), e);
            }
            finally {
                close(rs);
                close(connection);
            }
        }

        protected void close(ResultSet rs) {
            try { if( rs != null ) rs.close(); } catch(Exception e){}//ignore
        }
        protected void close(Connection connection) {
            try { if( connection != null ) connection.close(); } catch(Exception e){}//ignore
        }
    }
}
